//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft end-user
// license agreement (EULA) under which you licensed this SOFTWARE PRODUCT.
// If you did not accept the terms of the EULA, you are not authorized to use
// this source code. For a copy of the EULA, please see the LICENSE.RTF on your
// install media.
//

#include <windows.h>
#include <bsp.h>
#include "loader.h"
#include "nand.h"

extern IMAGE_TYPE        g_ImageType;

int g_SectorInfoSize = 0;

extern FLASH_CHIP_STRU g_stFlashChipInfo;

int BlflashWriteInBlock( void *pRamAddr, UINT32 ulFlashAddr, UINT32 copyBytes )
{
    DWORD         i            = 0;
    DWORD         dwTempLen    = copyBytes;    
    unsigned char *pSourceAddr = (unsigned char *)pRamAddr;
    unsigned char *pSectBuf    = NULL;
    PSectorInfo pSectInfoBuf   = NULL;
    
    for(i = 0;i < dwTempLen;i += NF_SECTOR_SIZE)
    {
        pSectBuf = pSourceAddr+i+(i*g_SectorInfoSize/NF_SECTOR_SIZE);
        if(sizeof(SectorInfo) == g_SectorInfoSize)
        {
            pSectInfoBuf = (PSectorInfo)(pSectBuf + NF_SECTOR_SIZE);
         }
        if(!FMD_WriteSector((ulFlashAddr + i)/NF_SECTOR_SIZE, pSectBuf, pSectInfoBuf, 1))
        {
            EdbgOutputDebugString("bLflashWrite flashWriteBufferBlock Error\r\n");
            return 1;
        }
    }    
    return 0; 
}

/*
    @func   BOOL | OEMIsFlashAddr | Tests whether the address provided resides in the Samsung's flash.
    @rdesc  TRUE = Specified address resides in flash, FALSE = Specified address doesn't reside in flash.
    @comm    
    @xref   
*/
BOOL OEMIsFlashAddr(DWORD dwAddr)
{
    // Does the specified address fall in the flash address range?
    //

    if ((dwAddr >= NAND_FLASH_START) && (dwAddr < NAND_FLASH_END))
    {
        return(TRUE);
    }
    return(FALSE);
}


/*
    @func   LPBYTE | OEMMapMemAddr | Remaps a specified address to a file cache location.  The file cache is used as a temporary store for flash images before they're written to flash.
    @rdesc  Corresponding address within a file cache area.
    @comm    
    @xref   
*/
LPBYTE OEMMapMemAddr(DWORD dwImageStart, DWORD dwAddr)
{
    UINT32 MappedAddr = dwAddr;

    OALMSG(OAL_FUNC, (TEXT("+OEMMapMemAddr (Address=0x%x).\r\n"), dwAddr));

    // If it's a flash address, translate it by "rebasing" the address into the file cache area.
    //
    if (OEMIsFlashAddr(dwAddr) && (dwImageStart <= dwAddr))
    {
        MappedAddr = (FILE_CACHE_START + (dwAddr - dwImageStart));
    }
    else if ((g_ImageType == IMAGE_LOADER) && (dwAddr >= EBOOT_RAM_IMAGE_BASE))
    {
        // Special case for bootloader - it's destined for flash, cached in the file cache area, but fixed-up to run from yet another RAM area (the romheader is "located" here).
        //
        MappedAddr = (FILE_CACHE_START + (dwAddr - EBOOT_RAM_IMAGE_BASE));
    }

    OALMSG(OAL_FUNC, (TEXT("-OEMMapMemAddr (Address=0x%x.\r\n"), MappedAddr));
    return((LPBYTE)MappedAddr);
}


/*
    @func   BOOL | OEMStartEraseFlash | Called at the start of image download, this routine begins the flash erase process.
    @rdesc  TRUE = Success, FALSE = Failure.
    @comm    
    @xref   
*/


BOOL OEMStartEraseFlash(DWORD dwStartAddr, DWORD dwLength)
{
    // Nothing to do (erase done in OEMWriteFlash)...
   
    return(TRUE);
}


/*
    @func   void | OEMContinueEraseFlash | Called frequenty during image download, this routine continues the flash erase process.
    @rdesc  N/A.
    @comm    
    @xref   
*/
void OEMContinueEraseFlash(void)
{
    // Nothing to do (erase done in OEMWriteFlash)...
  
}


/*
    @func   BOOL | OEMFinishEraseFlash | Called following the image download, this routine completes the flash erase process.
    @rdesc  TRUE = Success, FALSE = Failure.
    @comm    
    @xref   
*/
BOOL OEMFinishEraseFlash(void)
{
    // Nothing to do (erase done in OEMWriteFlash)...
   
    return(TRUE);
}

BOOL OEMWriteFlash(DWORD dwStartAddr, DWORD dwLength)
{
    DWORD i               = 0;
    DWORD j               = 0;
    DWORD ret             = 0;
    DWORD Size            = 0;
    DWORD TempLen         = dwLength;
    DWORD dwTotalBlock    = 0;
    unsigned char testbuf1[NF_SECTOR_SIZE+NF_SPARE_SIZE] = {0xFF};
    unsigned char testbuf2[NF_SPARE_SIZE] = {0xFF};
    PBYTE pCachedData = OEMMapMemAddr(dwStartAddr, dwStartAddr);
    PBYTE pTempCachedData = pCachedData;
    DWORD dwTempStartAddr = 0;
    DWORD dwReservedAddr = dwTempStartAddr = dwStartAddr - dwStartAddr%FLASH_BLOCK_SIZE;
    
    EdbgOutputDebugString("[OEMWriteFlash] version B831 1.2\r\ndwStartAddr = 0x%x,dwLength = 0x%x\r\n", dwStartAddr, dwLength);
    
    dwTotalBlock = (DWORD)FMD_Init(NULL, NULL, NULL);
    
    EdbgOutputDebugString("[OEMWriteFlash]Total Block number %d.\r\n", dwTotalBlock);
    
    /*write eboot to flah*/
    if(dwStartAddr == EBOOT_IMG_ADDR)
    {
        ret = FMD_EraseBlock(0);
        if(TRUE != ret)
        {
            EdbgOutputDebugString("[ERROR]:NF_BlockErase Error!ret = 0x%x\r\n", ret);
            return FALSE;
        }
        
        for(i = 0;i*NF_SECTOR_SIZE < dwLength;i++)
        {             
            memcpy(testbuf1, (void* )(pTempCachedData+i*NF_SECTOR_SIZE), NF_SECTOR_SIZE);
            ret = FMD_WriteSector(i, testbuf1, NULL, 1);
            if(TRUE != ret)
            {
                EdbgOutputDebugString("[ERROR]:NF_PageProgram Error!Addr = 0x%x,ret = 0x%x\r\n", i, ret);
                return FALSE;
            }
        }
        
        for(i = 0;i*NF_SECTOR_SIZE < dwLength;i++)
        {
            ret = FMD_ReadSector(i, testbuf1, NULL, 1);
            if(TRUE != ret)
            {
                EdbgOutputDebugString("[ERROR]:NF_PageRead Error!Addr = 0x%x,ret = 0x%x\r\n", i, ret);
                return FALSE;
            }
            if(0 != memcmp((void*)(pTempCachedData+i*NF_SECTOR_SIZE), testbuf1, NF_SECTOR_SIZE))
            {
                EdbgOutputDebugString("[ERROR]:memcmp Error\r\n");
                return FALSE;
            }
        }
    }
    else/*write img or ipl to flah*/
    {  
        if(dwStartAddr != IPL_IMG_ADDR)
        {
            g_SectorInfoSize = sizeof(SectorInfo);
            dwReservedAddr = dwTempStartAddr = IMG_START_ADDR;
            EdbgOutputDebugString("[INFO OEMWriteFlash]write IMG to 0x%x.\r\n",IMG_START_ADDR);
        }
        else
        {
            g_SectorInfoSize = 0;
            EdbgOutputDebugString("[INFO OEMWriteFlash]write IPL to 0x20000.\r\n");
        }

        //erase the img region
        if(sizeof(SectorInfo) == g_SectorInfoSize)
        {
            for(i = IMG_START_BLOCK; i < dwTotalBlock;i++)
            {
                if(!FMD_EraseBlock(i))
                {
                	EdbgOutputDebugString("[WARN OEMWriteFlash] FMD_EraseBlock find a bad block = %d.\r\n", i);
                    if(!FMD_SetBlockStatus(i, BLOCK_STATUS_BAD))
                    {
                    	EdbgOutputDebugString("[ERROR OEMWriteFlash] FMD_SetBlockStatus error = %d.\r\n", i);
                    }
                }
            }
        }
        else//erase the IPL region
        {
            for(i = 1; i < PARA_START_BLOCK;i++)
            {
                if(!FMD_EraseBlock(i))
                {
                	EdbgOutputDebugString("[WARN OEMWriteFlash] FMD_EraseBlock find a bad block = %d.\r\n", i);
                    if(!FMD_SetBlockStatus(i, BLOCK_STATUS_BAD))
                    {
                    	EdbgOutputDebugString("[ERROR OEMWriteFlash] FMD_SetBlockStatus error = %d.\r\n", i);
                    }
                }
            }
        }
        
        /*write img or ipl to flah*/
        while(TempLen)
        {
            if(TempLen >= FLASH_BLOCK_SIZE)
            {
                Size = FLASH_BLOCK_SIZE;
            }
            else
            {
                Size = TempLen;
            }

            if (FMD_GetBlockStatus(dwTempStartAddr/FLASH_BLOCK_SIZE) & BLOCK_STATUS_BAD) 
            {
                EdbgOutputDebugString("[ERROR OEMWriteFlash] FMD_GetBlockStatus find a bad block = 0x%x\r\n", dwTempStartAddr/FLASH_BLOCK_SIZE);
                dwTempStartAddr += FLASH_BLOCK_SIZE;            
                continue;
            }
            
            
            // Write IPL image
            if(0 != BlflashWriteInBlock(pTempCachedData, dwTempStartAddr, Size))
            {
                EdbgOutputDebugString("[ERROR OEMWriteFlash]BlflashWriteInBlock find a bad block = 0x%x\r\n", dwTempStartAddr/FLASH_BLOCK_SIZE);
                FMD_SetBlockStatus(dwTempStartAddr/FLASH_BLOCK_SIZE, BLOCK_STATUS_BAD);
                dwTempStartAddr += FLASH_BLOCK_SIZE;
                continue;
            }

            if(FLASH_BLOCK_SIZE == Size)
            {
                TempLen = TempLen - Size - g_SectorInfoSize*NF_SECTORS_PER_BLOCK;
                pTempCachedData += (Size + g_SectorInfoSize*NF_SECTORS_PER_BLOCK);
            }
            else
            {
                TempLen = TempLen - Size;
            }        
             dwTempStartAddr += Size;
        }
        
        /*compare the written data*/
        EdbgOutputDebugString("[INFO OEMWriteFlash]Compare begin.\r\n");
        for(i = dwReservedAddr,TempLen = 0;(TempLen+(TempLen*g_SectorInfoSize/NF_SECTOR_SIZE)) < (dwLength-NF_SECTOR_SIZE);)
        {
            if(i%FLASH_BLOCK_SIZE == 0)
            {
                if (FMD_GetBlockStatus(i/FLASH_BLOCK_SIZE) & BLOCK_STATUS_BAD) 
                {
                    EdbgOutputDebugString("[WARN OEMWriteFlash]find a bad block = 0x%x\r\n", i/FLASH_BLOCK_SIZE);
                    i += FLASH_BLOCK_SIZE;            
                    continue;
                }
            }
            
            if(!FMD_ReadSector(i/NF_SECTOR_SIZE, testbuf1, (PSectorInfo)testbuf2, 1))
            {
                EdbgOutputDebugString("[ERROR OEMWriteFlash]Error while FMD_ReadSector.\r\n");
                break;
            }

            if(0 != memcmp(testbuf1, pCachedData+TempLen+(TempLen*g_SectorInfoSize/NF_SECTOR_SIZE), NF_SECTOR_SIZE))
            {
                EdbgOutputDebugString("[ERROR OEMWriteFlash]Data error addr = 0x%x.\r\n", TempLen);
            }

            if((sizeof(SectorInfo) == g_SectorInfoSize)&&(0 != memcmp(testbuf2, pCachedData+TempLen+NF_SECTOR_SIZE+(TempLen*g_SectorInfoSize/NF_SECTOR_SIZE), sizeof(SectorInfo))))
            {
                EdbgOutputDebugString("[ERROR OEMWriteFlash]spare error addr = 0x%x.\r\n", TempLen+NF_SECTOR_SIZE);
            }

            i += NF_SECTOR_SIZE;
            TempLen += NF_SECTOR_SIZE;
        }
        EdbgOutputDebugString("[INFO OEMWriteFlash]Compare end.\r\n");
    }
    return(TRUE);
}



